package lumis.doui.source;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;

import lumis.portal.PortalException;
import lumis.portal.UnexpectedException;
import lumis.portal.authentication.SessionConfig;
import lumis.portal.stability.StableMinor;
import lumis.util.ITransaction;
import lumis.util.XmlUtil;

import org.w3c.dom.Node;

/**
 * Implements a provider for objects whose properties follows the 
 * java bean accessor methods convention.
 * <p>
 * The fields id in the source definition are used as the object's properties
 * names. For each one, its getter method is queried in the loaded object,
 * and the value is used to populate a tabular data for the source.
 * @param <S> the source class using this provider. If there is no need
 * for source specialization, may be just TabularSource.
 * @param <T> the class or interface the object's properties accessor methods
 * will be accessed through.
 * @since 4.0.4
 * @version $Revision: 13082 $ $Date: 2011-05-27 16:03:12 -0300 (Fri, 27 May 2011) $
 */
@StableMinor(version = "6.0", sinceVersion = "4.0")
public abstract class AbstractObjectDataProvider<S extends TabularSource, T> implements IDataProvider<S>
{
	/**
	 * The interface or class whose methods will be used to access the object properties.
	 */
	private final Class<? extends T> objectClass;

	/**
	 * Creates a new object data provider for objects of the given class or interface.
	 * @param objectClass the class or interface that contains the methods to
	 * read the object's properties.
	 * @since 4.0.4
	 */
	public AbstractObjectDataProvider(Class<? extends T> objectClass)
	{
		this.objectClass = objectClass;
	}
	
	/**
	 * Loads the objects. The properties of the objects returned will be used
	 * to populate the source's tabular data.
	 * @param sessionConfig the user session information.
	 * @param source the source that will be populated.
	 * @param transaction the transaction for persistence access.
	 * @return a list of objects with the data to populate the source.
	 * @throws PortalException
	 * @see {@link #getTotalRows(SessionConfig, TabularSource, ITransaction)}
	 * @since 4.0.4
	 */
	protected abstract List<T> loadObjects(SessionConfig sessionConfig,
			S source, ITransaction transaction) throws PortalException;
	
	/**
	 * Returns the total rows to be set on the generated tabular data.
	 * <p>
	 * If the value returned is a negative value, the total rows on the
	 * tabular data is not set. This is the default implementation. 
	 * @param sessionConfig the user session information.
	 * @param source the source that will be populated.
	 * @param transaction the transaction for persistence access.
	 * @return the total rows to be set on the generated tabular data,
	 * or a negative value if it should not be set.
	 * @throws PortalException
	 * @since 4.0.5
	 */
	protected int getTotalRows(SessionConfig sessionConfig, S source,
			ITransaction transaction) throws PortalException
	{
		return -1;
	}

	public void loadData(SessionConfig sessionConfig, S source,
			ITransaction transaction) throws PortalException
	{
		// load the objects
		List<T> objects = loadObjects(sessionConfig, source, transaction);

		if (objects != null)
		{
			TabularData data = source.getData();
			Node[] fieldNodes = XmlUtil.selectNodes("fields/field", source
					.getDefinitionNode());

			for (T object: objects)
			{
				ISourceData row = data.addRow();
				
				// copy pojo data into the source's tabulardata
				for (Node fieldNode : fieldNodes)
				{
					String fieldId = XmlUtil.readAttributeString("id", fieldNode);
					if (fieldId != null)
					{
						Object fieldValue = readProperty(object, fieldId);
						if (fieldValue != null)
							row.put(fieldId, fieldValue);
					}
				}
			}

			int totalRows = getTotalRows(sessionConfig, source, transaction);
			if (totalRows > 0)
				data.setTotalRows(totalRows);
		}			
	}

	/**
	 * Reads the property value of an object.
	 * <p>
	 * If the propertyName is <code>propertyName</code>, this method tries to read 
	 * the value from methods named <code>getPropertyName</code> or <code>isPropertyName</code>,
	 * with no parameters. 
	 * @param object the object instance.
	 * @param propertyName the property name.
	 * @return the property value or null if its accessor method could not be accessed.
	 * @throws UnexpectedException if there was an exception thrown by the accessor method.
	 * @since 4.0.4
	 */
	protected Object readProperty(T object, String propertyName)
			throws UnexpectedException
	{
		try
		{
			String methodNameSuffix = propertyName.substring(0, 1)
					.toUpperCase()
					+ propertyName.substring(1);
			String getMethodName = "get" + methodNameSuffix;
			Method getMethod;
			try
			{
				getMethod = objectClass.getMethod(getMethodName,
						(Class[]) null);
			}
			catch (NoSuchMethodException e)
			{
				// try using "is" instead of "get"
				getMethodName = "is" + methodNameSuffix;
				getMethod = objectClass.getMethod(getMethodName,
						(Class[]) null);
			}

			try
			{
				getMethod.setAccessible(true);
				Object value = getMethod.invoke(object, (Object[]) null);
				return value;
			}
			catch (InvocationTargetException e)
			{
				throw new UnexpectedException(e);
			}
			catch (Exception e)
			{
				// could not access the get method for the object. Return null
				return null;
			}
		}
		catch (NoSuchMethodException e)
		{
			// method not found... return null
			return null;
		}
	}
}
